
#include "HashManager.h"

HashManager::HashManager(string pathBloque, string pathTabla, int sizeBloque){
	this->pathBloque = pathBloque;
	this->pathTabla = pathTabla;
	this->sizeBloque = sizeBloque;
	this->filebuckets = new ArchivoDeBloques(pathBloque,sizeBloque);
	this->hashTable = new HashTable(pathTabla);
}

HashManager::~HashManager()
{
	delete filebuckets;
	delete hashTable;
}

bool HashManager::agregarRegistro(Registro* registro, Bloque* bloque, int offset){

	if(bloque->agregarRegistro(registro)) {
		filebuckets->agregarBloque(offset, bloque);  // guarda el bloque modificado en el archivo
		return true;
	}
	return false;
}

bool HashManager::agregarRegistro(Registro* registro){
	// cuando crea un bloque nuevo mira si hay libres
	if(hashTable->getTamanioTabla() == 0){  // si es el primer elemento creo el bloque 0
		Bloque* blq = new Bloque(sizeBloque);
		hashTable->setIdBucket(0, filebuckets->agregarBloque(blq));
		delete blq;
	}

	int pos = hashTable->getHash(registro->getId());  // obtengo el hash de la clave del registro
	int offset = hashTable->getIdBucket(pos);
	int disp = 0;
	int posaux = 0;
	Bloque* bloque = filebuckets->obtenerBloque(offset);
	if(bloque->buscarRegistro(registro)){  // valida unicidad
		delete bloque;
		return false;
	}
	if(!agregarRegistro(registro, bloque, offset)){
		Bloque* bloqueNuevo = new Bloque(sizeBloque);  // crear bloque nuevo
		int idBloqueNuevo = filebuckets->getBloqueLibre();
		if(idBloqueNuevo == -1)
			idBloqueNuevo = filebuckets->agregarBloque(bloqueNuevo);
		if(hashTable->getTamanioTabla() == bloque->getTamanioDispersion()){
			hashTable->duplicar(pos, idBloqueNuevo);
			disp = bloque->getTamanioDispersion()*2;
			bloque->setTamanioDispersion(disp);
			bloqueNuevo->setTamanioDispersion(disp);
			redispersar(bloque, offset, bloqueNuevo, idBloqueNuevo, registro);
		}
		else {
			if(hashTable->getTamanioTabla() > bloque->getTamanioDispersion()){
				int nuevaDispersion = bloque->getTamanioDispersion()*2;
				bloque->setTamanioDispersion(nuevaDispersion);
				bloqueNuevo->setTamanioDispersion(nuevaDispersion);
				posaux= pos;
				if (pos == hashTable->getTamanioTabla()-1){
					pos= nuevaDispersion -1;
					while(pos< hashTable->getTamanioTabla() && pos!= posaux ){
						hashTable->setIdBucket(pos,idBloqueNuevo);
						pos+= nuevaDispersion;
					}
				}
				else {
					bool aux = true;
					int aux2 = pos;
					int tamanioTabla = hashTable->getTamanioTabla();
					while(pos< tamanioTabla-1 && aux){

						hashTable->setIdBucket(pos,idBloqueNuevo);
						pos+= nuevaDispersion;

						if(pos < hashTable->getTamanioTabla()){
							hashTable->setIdBucket(pos,idBloqueNuevo);
							pos+= nuevaDispersion;
						}
						else {
							if (pos >= aux2)
								aux=false;
							else{
								pos = pos - tamanioTabla;
								hashTable->setIdBucket(pos,idBloqueNuevo);
								pos+= nuevaDispersion;
							}
						}
					}
				}
				redispersar(bloque,offset,bloqueNuevo,idBloqueNuevo, registro);
			}
		}
		delete bloqueNuevo;
	}
	delete bloque;
	return true;
}

void HashManager::redispersar(Bloque* bloque, int idBloque, Bloque* bloqueNuevo, int idBloqueNuevo, Registro* registro){
	Bloque* bloqueaux = new Bloque(sizeBloque);
	int idBlq = -1;
	int disp= bloque->getTamanioDispersion();
	bloqueaux->setTamanioDispersion(disp);

	vector<Registro*>* registros = bloque->getRegistros();
	for(int i= 0; i< (int)registros->size();i++){
		Registro* reg= (*registros)[i];
		idBlq =hashTable->obtenerIdBucket(reg->getId());
		if (idBlq == idBloque)
			bloqueaux->agregarRegistro(reg);
		else
			bloqueNuevo->agregarRegistro(reg);
	}

	int pos = hashTable->getHash(registro->getId());
	idBlq = hashTable->obtenerIdBucket(registro->getId());
	if (idBlq == idBloque){
		if(!bloqueaux->agregarRegistro(registro)){
			filebuckets->modificarBloque(idBloqueNuevo, bloqueNuevo);
			Bloque* bloqueNuevo2 =new Bloque(sizeBloque);
			idBloqueNuevo = filebuckets->getBloqueLibre();
			if (idBloqueNuevo == -1)
				idBloqueNuevo = filebuckets->agregarBloque(bloqueNuevo2);
			hashTable->duplicar(pos,idBloqueNuevo);
			bloqueaux->setTamanioDispersion(disp*2);
			bloqueNuevo2->setTamanioDispersion(disp*2);
			redispersar(bloqueaux,idBloque, bloqueNuevo2, idBloqueNuevo, registro);
			delete bloqueNuevo2;
		}
		else{
			filebuckets->modificarBloque(idBloque, bloqueaux);
			filebuckets->modificarBloque(idBloqueNuevo, bloqueNuevo);
		}
	}
	else{
		int idBloqueNuevo2 = -1;
		if(!bloqueNuevo->agregarRegistro(registro)){
			filebuckets->modificarBloque(idBloque, bloqueaux);
			Bloque* bloqueNuevo2 =new Bloque(sizeBloque);
			idBloqueNuevo2 = filebuckets->getBloqueLibre();
			if (idBloqueNuevo2 == -1)
				idBloqueNuevo2 = filebuckets->agregarBloque(bloqueNuevo2);
			hashTable->duplicar(pos,idBloqueNuevo);
			disp = bloqueNuevo->getTamanioDispersion()*2;
			bloqueNuevo->setTamanioDispersion(disp);
			bloqueNuevo2->setTamanioDispersion(disp);
			redispersar(bloqueNuevo,idBloqueNuevo, bloqueNuevo2, idBloqueNuevo2, registro);
			delete bloqueNuevo2;
		}
		else{
			filebuckets->modificarBloque(idBloque, bloqueaux);
			filebuckets->modificarBloque(idBloqueNuevo, bloqueNuevo);
		}
	}
	delete bloqueaux;
}

bool HashManager::buscarRegistro(int clave, Registro* buscado){
	bool encontrado = false;
	int idBloque = hashTable->getIdBucket(hashTable->getHash(clave));
	Bloque* bloque = filebuckets->obtenerBloque(idBloque);
	Registro* registro = new Registro(clave);
	if(bloque->buscarRegistro(registro)){
		buscado->clonar(registro);
		encontrado = true;
	}
	delete bloque;
	delete registro;
	return encontrado;
}

bool HashManager::modificarRegistro(Registro* registro){
	int idBloque = hashTable->getIdBucket(hashTable->getHash(registro->getId()));
	Bloque* bloque = filebuckets->obtenerBloque(idBloque);
	if(bloque->modificarRegistro(registro))
	{
		filebuckets->modificarBloque(idBloque,bloque);
		delete bloque;
		return true;
	}
	delete bloque;
	return false;
}

bool HashManager::eliminarRegistro(int clave){
	bool elimino = false;
	if(filebuckets->getCantidadRegistros() > 0) {
		Registro* registro = new Registro(clave);

		//Busco el registro en la hashTable.
		int posicionTabla = hashTable->getHash(registro->getId());

		//Levanto el idBucket de la posicion de la tabla.
		int idBucket = hashTable->getIdBucket(posicionTabla);

		//Obtengo el bucket del archivoBuckets.
		Bloque* bucket = filebuckets->obtenerBloque(idBucket);

		//Elimino el registro del bucket.
		if(bucket->eliminarRegistro(registro))
		{
			//Vemos si quedo vacio el bucket.
			if(bucket->getCantidadRegistros() == 0)
			{
				int idBucketReemplazado = hashTable->liberarBloque(bucket->getTamanioDispersion(),posicionTabla);
				//Me fijo si se puede liberar el bloque.
				if(idBucketReemplazado != -1)
				{
					//Obtengo el bucket que reemplazo y divido su tDispersion.
					Bloque* bucket2 = filebuckets->obtenerBloque(idBucketReemplazado);
					bucket2->setTamanioDispersion(bucket2->getTamanioDispersion()/2);
					filebuckets->modificarBloque(idBucketReemplazado,bucket2);
					delete bucket2;
					//Agrego a los bloques libres el idBucket que fue liberado.
					filebuckets->setBloqueLibre(idBucket);
				}
			}
			elimino = true;
			filebuckets->modificarBloque(idBucket,bucket);
		}
		delete bucket;
		delete registro;
	}
	return elimino;
}
